/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */

package io.iec.edp.caf.multicontext.factory;

import io.iec.edp.caf.multicontext.config.ParallelConfigReader;
import io.iec.edp.caf.multicontext.config.ParallelSetting;
import io.iec.edp.caf.multicontext.context.PlatformApplicationContext;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.AttributeAccessor;
import org.springframework.core.ResolvableType;
import org.springframework.core.type.MethodMetadata;
import org.springframework.lang.Nullable;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentMap;

/**
 * 模块自有的BeanFactory
 *
 * @author guowenchang
 */
@Slf4j
public class ModuleBeanFactory extends DefaultListableBeanFactory {
    private final String JPA_REPO_EXTENSION_NAME = "org.springframework.data.jpa.repository.config.JpaRepositoryConfigExtension#0";
    private final String CAF_REPO_EXTENSION_NAME = "io.iec.caf.data.jpa.repository.config.CafJpaRepositoryConfigExtension#0";
    public static final String JPA_CONTEXT_BEAN_NAME = "jpaContext";
    public static final String EM_BEAN_DEFINITION_REGISTRAR_POST_PROCESSOR_BEAN_NAME = "emBeanDefinitionRegistrarPostProcessor";
    private static final String JPA_METAMODEL_CACHE_CLEANUP_CLASSNAME = "org.springframework.data.jpa.util.JpaMetamodelCacheCleanup";
    private final ConcurrentMap<String, BeanWrapper> factoryBeanInstanceCache = getFactoryBeanInstanceCache();

    private ParallelSetting settting = ParallelConfigReader.readParallelSetting();

    private String moduleName;

    public ModuleBeanFactory(BeanFactory beanFactory,String moduleName) {
        super(beanFactory);
        this.moduleName = moduleName;
    }

    /**
     * 此处强识别了hibernate的一些bean 保证在整个进程中只能从底座中取到hibernate上下文
     * 以保证hibernate上下文的全局唯一性
     *
     * @param beanName
     * @return
     */
    @Override
    public boolean containsBeanDefinition(String beanName) {
        if (CAF_REPO_EXTENSION_NAME.equals(beanName)
                //                || ENTITY_SCAN_PACKAGES.equals(beanName)
                //                || JPA_MAPPING_CONTEXT_BEAN_NAME.equals(beanName)
                || JPA_REPO_EXTENSION_NAME.equals(beanName)
                || JPA_CONTEXT_BEAN_NAME.equals(beanName)
                || JPA_METAMODEL_CACHE_CLEANUP_CLASSNAME.equals(beanName)
                || EM_BEAN_DEFINITION_REGISTRAR_POST_PROCESSOR_BEAN_NAME.equals(beanName)) {
            return true;
        }

        return super.containsBeanDefinition(beanName);
    }

    //加锁 防止死锁
    //todo 后期实现模块间完全隔离后 可将相关逻辑全部去掉 性能可以得到大幅度提升
    @Override
    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
//        synchronized (this.getParentBeanFactory()) {
        return super.getSingleton(beanName, singletonFactory);
//        }
    }

//    //集中包扫描路径配置
//    @Override
//    public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
//        if (ENTITY_SCAN_PACKAGES.equals(beanName)) {
//            return ((DefaultListableBeanFactory) this.getParentBeanFactory()).getBeanDefinition(beanName);
//        }
//
//        return super.getBeanDefinition(beanName);
//    }


    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {
        var specialBeans = new ArrayList<String>(){{
            add("wfTaskServerEndpointExporter");//WF注册了一个ServletContainer 必须在WebApplication中才能用 因此要注册到底座里去
            add("serverEndpointExporter");//消息注册了一个ServletContainer 必须在WebApplication中才能用 因此要注册到底座里去
            //add("getWebSocket");
        }};

//        specialBeans.addAll(settting.getSpecilBeans());


        if(beanDefinition.getSource()!=null && beanDefinition.getSource() instanceof MethodMetadata){
            String returnTypeName = ((MethodMetadata)beanDefinition.getSource()).getReturnTypeName();
            if(((MethodMetadata)beanDefinition.getSource()).getReturnTypeName().equalsIgnoreCase("org.springframework.web.socket.server.standard.ServerEndpointExporter")){
                specialBeans.add(beanName);
            } else if(((MethodMetadata)beanDefinition.getSource()).getReturnTypeName().equalsIgnoreCase("io.iec.edp.caf.core.context.BizContextBuilder")) {
                specialBeans.add(beanName);
            }
//            else if(((MethodMetadata)beanDefinition.getSource()).getReturnTypeName().equalsIgnoreCase("io.iec.edp.caf.rest.RESTEndpoint")){
//                StartupAnalysisLog.countRestBean(moduleName);
//            }else if((MethodMetadata)((MethodMetadata) beanDefinition.getSource()).getAnnotationAttributes("RpcService")!=null){
//                StartupAnalysisLog.countRpcBean(moduleName);
//            }
        }

        //这些特殊的bean需要注册到底座里，当面主要的是基于原生java的websocket类型的,
        if(specialBeans.contains(beanName)){
            log.info("当前websocket的bean:"+beanName);
            ((DefaultListableBeanFactory) getParentBeanFactory()).registerBeanDefinition(beanName, beanDefinition);
        } else {
            super.registerBeanDefinition(beanName, beanDefinition);
        }
    }

    //暴露非public方法
    @Override
    public <T> T doGetBean(
            String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
            throws BeansException {
        return super.doGetBean(name, requiredType, args, typeCheckOnly);
    }

    /**
     * configurationReport是用于处理conditional条件的工具类
     * 由于无法将底座和module的configClass放在一起初始化 因此该方法暂时不需要了
     */
//    @Override
//    public boolean containsSingleton(String beanName) {
//        if (beanName.equals(AUTO_CONFIG_REPORT_NAME)) {
//            return ((PlatformBeanFactory) this.getParentBeanFactory()).containsSingleton(AUTO_CONFIG_REPORT_NAME);
//        } else {
//            return super.containsSingleton(beanName);
//        }
//    }

//    @Override
//    public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
//        if (beanName.equals(AUTO_CONFIG_REPORT_NAME)) {
//            ((PlatformBeanFactory) this.getParentBeanFactory()).registerSingleton(AUTO_CONFIG_REPORT_NAME, singletonObject);
//        } else {
//            super.registerSingleton(beanName, singletonObject);
//        }
//    }

    ResolvableType getTypeForFactoryBeanFromAttributes(AttributeAccessor attributes) {
        Object attribute = attributes.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE);
        if (attribute instanceof ResolvableType) {
            return (ResolvableType) attribute;
        }
        if (attribute instanceof Class) {
            return ResolvableType.forClass((Class<?>) attribute);
        }
        return ResolvableType.NONE;
    }

    @Override
    protected ResolvableType getTypeForFactoryBean(String beanName, RootBeanDefinition mbd, boolean allowInit) {
        // Check if the bean definition itself has defined the type with an attribute
        ResolvableType result = getTypeForFactoryBeanFromAttributes(mbd);
        if (result != ResolvableType.NONE) {
            return result;
        }

        ResolvableType beanType =
                (mbd.hasBeanClass() ? ResolvableType.forClass(mbd.getBeanClass()) : ResolvableType.NONE);

        // For instance supplied beans try the target type and bean class
        if (mbd.getInstanceSupplier() != null) {
            result = getFactoryBeanGeneric(getTargetType(mbd));
            if (result.resolve() != null) {
                return result;
            }
            result = getFactoryBeanGeneric(beanType);
            if (result.resolve() != null) {
                return result;
            }
        }

        // Consider factory methods
        String factoryBeanName = mbd.getFactoryBeanName();
        String factoryMethodName = mbd.getFactoryMethodName();

        // Scan the factory bean methods
        if (factoryBeanName != null) {
            if (factoryMethodName != null) {
                // Try to obtain the FactoryBean's object type from its factory method
                // declaration without instantiating the containing bean at all.
                BeanDefinition factoryBeanDefinition = getBeanDefinition(factoryBeanName);
                Class<?> factoryBeanClass;
                if (factoryBeanDefinition instanceof AbstractBeanDefinition &&
                        ((AbstractBeanDefinition) factoryBeanDefinition).hasBeanClass()) {
                    factoryBeanClass = ((AbstractBeanDefinition) factoryBeanDefinition).getBeanClass();
                } else {
                    RootBeanDefinition fbmbd = getMergedBeanDefinition(factoryBeanName, factoryBeanDefinition);
                    factoryBeanClass = determineTargetType(factoryBeanName, fbmbd);
                }
                if (factoryBeanClass != null) {
                    result = getTypeForFactoryBeanFromMethod(factoryBeanClass, factoryMethodName);
                    if (result.resolve() != null) {
                        return result;
                    }
                }
            }
            // If not resolvable above and the referenced factory bean doesn't exist yet,
            // exit here - we don't want to force the creation of another bean just to
            // obtain a FactoryBean's object type...
            if (!isBeanEligibleForMetadataCaching(factoryBeanName)) {
                return ResolvableType.NONE;
            }
        }

        // If we're allowed, we can create the factory bean and call getObjectType() early
        if (allowInit) {
            FactoryBean<?> factoryBean = (mbd.isSingleton() ?
                    getSingletonFactoryBeanForTypeCheck(beanName, mbd) :
                    getNonSingletonFactoryBeanForTypeCheck(beanName, mbd));
            if (factoryBean != null) {
                // Try to obtain the FactoryBean's object type from this early stage of the instance.
                Class<?> type = getTypeForFactoryBean(factoryBean);
                if (type != null) {
                    return ResolvableType.forClass(type);
                }
                // No type found for shortcut FactoryBean instance:
                // fall back to full creation of the FactoryBean instance.
                return super.getTypeForFactoryBean(beanName, mbd, true);
            }
        }

        if (factoryBeanName == null && mbd.hasBeanClass() && factoryMethodName != null) {
            // No early bean instantiation possible: determine FactoryBean's type from
            // static factory method signature or from class inheritance hierarchy...
            return getTypeForFactoryBeanFromMethod(mbd.getBeanClass(), factoryMethodName);
        }
        result = getFactoryBeanGeneric(beanType);
        if (result.resolve() != null) {
            return result;
        }
        return ResolvableType.NONE;
    }

    @Nullable
    private FactoryBean<?> getNonSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) {
        if (isPrototypeCurrentlyInCreation(beanName)) {
            return null;
        }

        Object instance;
        try {
            // Mark this bean as currently in creation, even if just partially.
            beforePrototypeCreation(beanName);
            // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
            instance = resolveBeforeInstantiation(beanName, mbd);
            if (instance == null) {
                BeanWrapper bw = createBeanInstance(beanName, mbd, null);
                instance = bw.getWrappedInstance();
            }
        } catch (UnsatisfiedDependencyException ex) {
            // Don't swallow, probably misconfiguration...
            throw ex;
        } catch (BeanCreationException ex) {
            // Instantiation failure, maybe too early...
            if (logger.isDebugEnabled()) {
                logger.debug("Bean creation exception on non-singleton FactoryBean type check: " + ex);
            }
            onSuppressedException(ex);
            return null;
        } finally {
            // Finished partial creation of this bean.
            afterPrototypeCreation(beanName);
        }

        return getFactoryBean(beanName, instance);
    }

    @Nullable
    private FactoryBean<?> getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) {
        BeanWrapper bw = this.factoryBeanInstanceCache.get(beanName);
        if (bw != null) {
            return (FactoryBean<?>) bw.getWrappedInstance();
        }
        Object beanInstance = getSingleton(beanName, false);
        if (beanInstance instanceof FactoryBean) {
            return (FactoryBean<?>) beanInstance;
        }
        if (isSingletonCurrentlyInCreation(beanName) ||
                (mbd.getFactoryBeanName() != null && isSingletonCurrentlyInCreation(mbd.getFactoryBeanName()))) {
            return null;
        }

        Object instance;
        try {
            // Mark this bean as currently in creation, even if just partially.
            beforeSingletonCreation(beanName);
            // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
            instance = resolveBeforeInstantiation(beanName, mbd);
            if (instance == null) {
                bw = createBeanInstance(beanName, mbd, null);
                instance = bw.getWrappedInstance();
            }
        } catch (UnsatisfiedDependencyException ex) {
            // Don't swallow, probably misconfiguration...
            throw ex;
        } catch (BeanCreationException ex) {
            // Instantiation failure, maybe too early...
            if (logger.isDebugEnabled()) {
                logger.debug("Bean creation exception on singleton FactoryBean type check: " + ex);
            }
            onSuppressedException(ex);
            return null;
        } finally {
            // Finished partial creation of this bean.
            afterSingletonCreation(beanName);
        }

        FactoryBean<?> fb = getFactoryBean(beanName, instance);
        if (bw != null) {
            this.factoryBeanInstanceCache.put(beanName, bw);
        }
        return fb;
    }

    private ResolvableType getFactoryBeanGeneric(ResolvableType beanType) {
        try {
            Method method = AbstractAutowireCapableBeanFactory.class.getDeclaredMethod("getFactoryBeanGeneric", ResolvableType.class);
            method.setAccessible(true);
            return (ResolvableType) method.invoke(this, beanType);
        } catch (NoSuchMethodException e) {
            log.error(e.getMessage(),e);
        } catch (InvocationTargetException e) {
            log.error(e.getMessage(),e);
        } catch (IllegalAccessException e) {
            log.error(e.getMessage(),e);
        }

        return null;
    }

    private ResolvableType getTypeForFactoryBeanFromMethod(Class<?> clz, String name) {
        try {
            Method method = AbstractAutowireCapableBeanFactory.class.getDeclaredMethod("getTypeForFactoryBeanFromMethod", Class.class, String.class);
            method.setAccessible(true);
            return (ResolvableType) method.invoke(this, clz, name);
        } catch (NoSuchMethodException e) {
            log.error(e.getMessage(),e);
        } catch (InvocationTargetException e) {
            log.error(e.getMessage(),e);
        } catch (IllegalAccessException e) {
            log.error(e.getMessage(),e);
        }

        return null;
    }

    private ResolvableType getTargetType(RootBeanDefinition mbd) {
        Field field = null;
        try {
            field = RootBeanDefinition.class.getDeclaredField("targetType");
            field.setAccessible(true);
            ResolvableType resolvableType = (ResolvableType) field.get(mbd);
            return resolvableType;
        } catch (NoSuchFieldException | IllegalAccessException e) {
            log.error(e.getMessage(),e);
        }

        return null;
    }

    private ConcurrentMap<String, BeanWrapper> getFactoryBeanInstanceCache() {
        Field field = null;
        try {
            field = AbstractAutowireCapableBeanFactory.class.getDeclaredField("factoryBeanInstanceCache");
            field.setAccessible(true);
            return (ConcurrentMap<String, BeanWrapper>) field.get(this);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            log.error(e.getMessage(),e);
        }

        return null;
    }
}
